
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Getters, Setters and Validators</title>

<style type="text/css">
/*margin and padding on body element
  can introduce errors in determining
  element position and are not recommended;
  we turn them off as a foundation for YUI
  CSS treatments. */
body {
	margin:0;
	padding:0;
}
</style>

<link type="text/css" rel="stylesheet" href="../../build/cssfonts/fonts-min.css" />
<script type="text/javascript" src="../../build/yui/yui-min.js"></script>


<!--begin custom header content for this example-->
<style type="text/css">

    #boxParent {
        overflow:hidden;
        background-color:#004c6d;
        height:25em;
        padding:10px;
        margin:5px;
    }

    #boxParent .yui-box p, #attrs p {
        margin:2px;
    }

    .attrs {
        border:1px solid #000;
        background-color:#cdcdcd;
        margin:5px;
    }

    .attrs .header {
        font-weight:bold;
        background-color:#aaa;
        padding:5px;
    }

    .attrs .body {
        padding:10px;
    }
 
    .attrs .body .hints li {
        padding-bottom:10px;
    }

    .attrs .footer {
        padding:0px 20px 10px 20px;
    }

    .attrs label {
        font-weight:bold;
        display:block;
        float:left;
        width:4em;
    }

    .attrs .hint {
        font-size:85%;
        color: #004c6d;
    }

    .attrs .fields {
        border-top:1px solid #aaa;
        padding:10px;
    }

    .yui-box {
        padding:5px;
        border:1px solid #000;
        width:8em;
        height:8em;
        text-align:center;
        color:#000;
    }

    .yui-box .color, .yui-box .coord {
        font-family:courier;
    }

</style>

<!--end custom header content for this example-->

</head>

<body class=" yui-skin-sam">

<h1>Getters, Setters and Validators</h1>

<div class="exampleIntro">
	The <a href="attribute-basic.html">Basic Configuration</a> example shows how we can add attributes to a host class, and set up default values for them using the attribute configuration object. This example explores how you can configure <code>setter</code>, <code>getter</code> and <code>validator</code> functions for individual attributes, which can be used to modify or normalize attribute values during get and set invocations, and prevent invalid values from being stored. 


			
</div>

<!--BEGIN SOURCE CODE FOR EXAMPLE =============================== -->

<div id="attrs" class="attrs">
    <div class="header">Enter new values and click the "Set" buttons</div>
    <div class="body">
        <ul class="hints">
            <li>Try entering valid and invalid values for x, y; or values which attempt to position the box outside it's parent (parent box co-ordinates are displayed next to the text box).</li>
            <li>Try entering rgb, hex or keyword color values [ <code>rgb(255,0,0)</code>, <code>#ff0000</code>, <code>red</code> ].</li>
        </ul>
        <div class="fields">
            <p>
                <label for="x">x:</label>
                <input type="text" name="x" id="x" />
                <button type="button" class="action" id="setX">Set</button>
                <span id="xhint" class="hint"></span>
            </p>
            <p>
                <label for="y">y:</label>
                <input type="text" name="y" id="y" />
                <button type="button" class="action" id="setY">Set</button>
                <span id="yhint" class="hint"></span>
            </p>
            <p>
                <label for="color">color:</label>
                <input type="text" name="color" id="color" />
                <button type="button" class="action" id="setColor">Set</button>
            </p>
        </div>
    </div>
    <div class="footer">
        <button type="button" class="action" id="setXY">Set XY</button>
        <button type="button" class="action" id="setAll">Set All</button>
        <button type="button" class="action" id="getAll">Get All</button>
    </div>
</div>

<div id="boxParent"></div>

<script type="text/javascript">
// Get a new YUI instance 
YUI({base:"../../build/", timeout: 10000}).use("node", "attribute", function(Y) {

    // Shortcut for Y.Lang;
    var L = Y.Lang;

    var boxParent = Y.Node.get("#boxParent");

    // Setup a custom class with attribute support
    function Box(cfg) {
        this._createNode(cfg);

        this.addAttrs(Y.merge(Box.ATTRIBUTES), cfg);

        this._sync();
        this._bind();
    }

    Box.BUFFER = 5;

    // Create the box node
    Box.prototype._createNode = function() {
        this._node = Y.Node.create('<div class="yui-box"><p>Positioned Box</p><p class="coord"></p><p class="color">None</p></div>');
    };

    // Update rendered state to match the attribute state
    Box.prototype._sync = function() {
        this._syncParent();
        this._syncXY();
        this._syncColor();
    };
    
    Box.prototype._syncParent = function() {
        this.get("parent").appendChild(this._node);
    };

    Box.prototype._syncXY = function() {
        this._node.setXY(this.get("xy"));
        this._node.query("p.coord").set("innerHTML", "[" + this.get("x") + "," + this.get("y") + "]");
    };

    Box.prototype._syncColor = function() {
        this._node.setStyle("backgroundColor", this.get("color"));
        this._node.query("p.color").set("innerHTML", this.get("color"));
    };

    // Bind listeners for attribute change events
    Box.prototype._bind = function() {

        // Reflect any changes in xy, to the rendered Node
        this.after("xyChange", this._syncXY);

        // Reflect any changes in color, to the rendered Node
        // and output the color value received from get
        this.after("colorChange", this._syncColor);

        // Append the rendered node to the parent provided
        this.after("parentChange", this._syncParent);

    };

    // Get min, max unconstrained values for X. Using Math.round to handle FF3's sub-pixel region values
    Box.prototype.getXConstraints = function() {
        var parentRegion = this.get("parent").get("region");
        return [Math.round(parentRegion.left + Box.BUFFER), Math.round(parentRegion.right - this._node.get("offsetWidth") - Box.BUFFER)];
    };

    // Get min, max unconstrained values for Y.  Using Math.round to handle FF3's sub-pixel region values
    Box.prototype.getYConstraints = function() {
        var parentRegion = this.get("parent").get("region");
        return [Math.round(parentRegion.top + Box.BUFFER), Math.round(parentRegion.bottom - this._node.get("offsetHeight") - Box.BUFFER)];
    };

    // Constrain the x,y value provided
    Box.prototype.constrain = function(val) {

        // If the X value places the box outside it's parent,
        // modify it's value to place the box inside it's parent.

        var xConstraints = this.getXConstraints();

        if (val[0] < xConstraints[0]) {
            val[0] = xConstraints[0];
        } else {
            if (val[0] > xConstraints[1]) {
                val[0] = xConstraints[1];
            }
        }

        // If the Y value places the box outside it's parent,
        // modify it's value to place the box inside it's parent.

        var yConstraints = this.getYConstraints();

        if (val[1] < yConstraints[0]) {
            val[1] = yConstraints[0];
        } else {
            if (val[1] > yConstraints[1]) {
                val[1] = yConstraints[1];
            }
        }

        return val;
    };

    // Setup attribute configuration
    Box.ATTRIBUTES = {

        "parent" : {
            value: null
        },

        "x" : {
            setter: function(val, name) {
                // Pass through x value to xy
                this.set("xy", [parseInt(val), this.get("y")]);
            },

            getter: function(val, name) {
                // Get x value from xy
                return this.get("xy")[0];
            }
        },

        "y" : {
            setter: function(val, name) {
                // Pass through y value to xy
                this.set("xy", [this.get("x"), parseInt(val)]);
            },

            getter: function() {
                // Get y value from xy
                return this.get("xy")[1];
            }
        },

        "xy" : {
            // Actual stored xy co-ordinates
            value: [0, 0],

            setter: function(val, name) {
                // Constrain XY value to the parent element.

                // Returns the constrained xy value, which will
                // be the final value stored.
                return this.constrain(val);
            },

            validator: function(val, name) {
                // Ensure we only store a valid data value
                return (L.isArray(val) && val.length == 2 && L.isNumber(val[0]) && L.isNumber(val[1]));
            }
        },

        "color" : {
            value: "olive",

            getter: function(val, name) {
                // Always normalize the returned value to 
                // a hex color value,  even if the stored 
                // value is a keyword, or an rgb value.
                if (val) {
                    return Y.Color.toHex(val);
                } else {
                    return null;
                }
            },

            validator: function(val, name) {
                // Ensure we only store rgb, hex or keyword values.
                return (Y.Color.re_RGB.test(val) || Y.Color.re_hex.test(val) || Y.Color.KEYWORDS[val]); 
            }
        }
    };

    Y.augment(Box, Y.Attribute);

    // ------

    // Create a new instance of Box
    var box = new Box({
        parent : boxParent 
    });

    // Set references to form controls
    var xTxt = Y.Node.get("#x");
    var yTxt = Y.Node.get("#y");
    var colorTxt = Y.Node.get("#color");

    var xHint = Y.Node.get("#xhint");
    var yHint = Y.Node.get("#yhint");

    function getAll() {
        xTxt.set("value", box.get("x"));
        yTxt.set("value", box.get("y"));
        colorTxt.set("value", box.get("color"));
    }

    // Bind DOM events for Form Controls

    // Use event delegation for the action button clicks
    Y.on("delegate", function(e) {

        // Get Node target from the event object

        // We already know it's a button which has an action because
        // of our selector (button.action), so all we need to do is 
        // route it based on the id.
        var id = e.currentTarget && e.currentTarget.get("id");

        switch (id) {
            case "setXY":
                box.set("xy", [parseInt(xTxt.get("value")), parseInt(yTxt.get("value"))]);
                break;
            case "setX":
                box.set("x", parseInt(xTxt.get("value")));
                break;
            case "setY":
                box.set("y", parseInt(yTxt.get("value")));
                break;
            case "setColor":
                box.set("color", L.trim(colorTxt.get("value")));
                break;
            case "setAll":
                box.set("xy", [parseInt(xTxt.get("value")), parseInt(yTxt.get("value"))]);
                box.set("color", L.trim(colorTxt.get("value")));
                break;
            case "getAll":
                getAll();
                break;
            default:
                break;
        }

    }, "#attrs", "click", "button.action");

    // Bind listeners to provide min, max unconstrained value hints for x, y
    // (focus/blur doesn't bubble, so bind individual listeners)
    Y.on("focus", function() {
        var minmax = box.getXConstraints();
        xHint.set("innerHTML", "Valid values: " + minmax[0] + " to " + minmax[1]);
    }, xTxt);

    Y.on("focus", function() {
        var minmax = box.getYConstraints();
        yHint.set("innerHTML", "Valid values: " + minmax[0] + " to " + minmax[1]);
    }, yTxt);

    Y.on("blur", function() {
        xHint.set("innerHTML", "");
    }, xTxt);

    Y.on("blur", function() {
        yHint.set("innerHTML", "");
    }, yTxt);

    getAll();
});
</script>

<!--END SOURCE CODE FOR EXAMPLE =============================== -->

</body>
</html>
